<template>
  <div ref="componentRef" class="setting-web-component" :class="'setting-web-component-' + index"
    :data-component-key="props.element.componentKey" :data-component-id="props.element.id" :key="componentKey"
    @mouseover.stop="handleMouseOver" @mouseout.stop="handleMouseOut">
    <!-- 工具栏 -->
    <div class="toolbar" ref="toolbarRef" v-if="showToolBar">
      <div class="toolbar-wrap">
        <el-tooltip effect="dark" content="编辑" placement="top">
          <i class="iconfont" @click="handleEdit">&#xe6e6;</i>
        </el-tooltip>
        <el-tooltip effect="dark" content="上移" placement="top" v-if="index > 0" :hide-after="0">
          <i class="iconfont" @click="handleMoveUp">&#xe8d8;</i>
        </el-tooltip>
        <el-tooltip effect="dark" content="下移" placement="top" v-if="index < componentListLength - 1" :hide-after="0">
          <i class="iconfont" @click="handleMoveDown">&#xe8c9;</i>
        </el-tooltip>
        <el-tooltip effect="dark" content="删除" placement="top">
          <i class="iconfont" @click="handleDelete">&#xe74b;</i>
        </el-tooltip>
      </div>
    </div>

    <!-- 组件内容 -->
    <div ref="contentRef"></div>

    <!-- 布局添加按钮 -->
    <div v-if="addNum > 0">
      <div v-for="item in addNum" :key="item" class="layout-add-box">
        <el-button type="primary" size="large" @click="handleAddLayout">
          添加组件
        </el-button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed, watch, onUnmounted, nextTick } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTemplateDetail } from '@/api/template'
import dayjs from 'dayjs'

const props = defineProps({
  element: {
    type: Object,
    required: true
  },
  index: {
    type: Number,
    required: true
  },
  showToolBar: {
    type: Boolean,
    default: true
  },
  initRender: {
    type: Boolean,
    default: false
  }
})
const emits = defineEmits(['handleLayoutAdd', 'nextRender', 'mouseEvent'])
const store = useStore()
const componentKey = ref(dayjs().valueOf())
const toolbarRef = ref(null)
const contentRef = ref(null)
const componentRef = ref(null)
// 改变isShow的时候，不重新渲染
watch(() => ({
  isShow: props.element.isShow,
  content: props.element.content,
  children: props.element.children,
  componentKey: props.element.componentKey
}), async (newVal, oldVal) => {
  if (newVal.isShow !== oldVal?.isShow && 
  newVal.content === oldVal?.content && 
  newVal.componentKey === oldVal?.componentKey) {
    if (contentRef.value && contentRef.value.firstElementChild) {
      contentRef.value.firstElementChild.style.display = newVal.isShow ? '' : 'none'
    }
  } else {
    await renderContent()
    await loadComponentScripts()
  }
}, { deep: true })

// 计算是否显示布局添加按钮
const addNum = computed(() => {
  let num = 0, macro = props.element.macro, length = props.element.children ? props.element.children.length : 0
  if (macro === 'layout_single') {
    num = 1 - length
  } else if (macro === 'layout_double') {
    num = 2 - length
  }
  return num
})

const handleAddLayout = () => {
  let index = props.element.children ? props.element.children.length : 0
  emits('handleLayoutAdd', props.element.componentKey, index)
}

// 获取组件列表长度用于上下移动按钮显示判断
const componentListLength = computed(() => store.state.template.componentList.length)

// 处理鼠标悬停
const handleMouseOver = () => {
  if (toolbarRef.value) {
    toolbarRef.value.style.display = 'block'
  }
  if (props.showToolBar) {
    contentRef.value.style.outline = '1px dashed #409EFF'
  }
}

// 处理鼠标移出
const handleMouseOut = () => {
  if (toolbarRef.value) {
    toolbarRef.value.style.display = 'none'
  }
  contentRef.value.style.outline = 'none'
}

// 渲染组件内容的方法
const renderContent = async () => {
  if (!contentRef.value) return

  return new Promise((resolve) => {
    // 清空现有内容
    contentRef.value.innerHTML = ''

    if (props.element.content) {
      const container = document.createElement('div')
      container.innerHTML = props.element.content
      const element = container.firstElementChild

      // 根据isShow状态设置display
      if (!props.element.isShow) {
        element.style.display = 'none'
      } else {
        element.style.removeProperty('display')
      }

      // 递归渲染子组件
      if (props.element.children && props.element.children.length > 0) {
        const getLastNestedChild = (el) => {
          if (el && el.lastElementChild) {
            return getLastNestedChild(el.lastElementChild)
          }
          return el
        }

        const target = getLastNestedChild(element)
        if (target) {
          for (const child of props.element.children) {
            const childContainer = document.createElement('div')
            childContainer.innerHTML = child.content
            const childElement = childContainer.firstElementChild
            if (!child.isShow) {
              childElement.style.display = 'none'
            } else {
              childElement.style.removeProperty('display')
            }
            target.appendChild(childElement)
          }
        }
      }

      // 将内容添加到DOM
      contentRef.value.appendChild(element)
      componentRef.value.removeAttribute('data-processed')

      // 如果不在视口内，设置观察器
      if (!isInViewport(contentRef.value.firstElementChild)) {
        if (observer.value) {
          observer.value.disconnect()
        }
        setupIntersectionObserver()
      }
      resolve()
    } else {
      resolve()
    }
  })
}

const isInViewport = (element) => {
  const rect = element.getBoundingClientRect()
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  )
}

const loadComponentScripts = async () => {
  const { jsLinks = [] } = props.element
  const { commonResources } = store.state.base.groupSetting
  const basePath = process.env.NODE_ENV === 'development' 
    ? commonResources.libPath_development 
    : commonResources.libPath_production

  try {
    // 1. 等待确保DOM完全准备好
    await nextTick()

    // 2. 按顺序加载公共body脚本
    for (const path of commonResources.bodyJs) {
      const fullPath = basePath + path
      await new Promise((resolve, reject) => {
        const existingScript = document.head.querySelector(`script[src="${fullPath}"]`)
        if (existingScript) {
          existingScript.remove()
        }
        const newScript = document.createElement('script')
        newScript.src = fullPath
        newScript.onload = resolve
        newScript.onerror = reject
        document.head.appendChild(newScript)
      })
    }

    // 3. 按顺序处理内联脚本
    const parser = new DOMParser()
    const doc = parser.parseFromString(props.element.content, 'text/html')
    const scripts = Array.from(doc.getElementsByTagName('script'))

    // 分离有src和无src的脚本，无src的脚本（如裁剪函数）需要异步执行
    const srcScripts = scripts.filter(script => script.src)
    const contentScripts = scripts.filter(script => !script.src && script.textContent)

    for (const script of srcScripts) {
      await new Promise((resolve, reject) => {
        const existingScript = document.head.querySelector(`script[src="${script.src}"]`)
        if (existingScript) {
          existingScript.remove()
        }
        const newScript = document.createElement('script')
        newScript.src = script.src
        newScript.onload = resolve
        newScript.onerror = reject
        document.head.appendChild(newScript)
      })
    }

    contentScripts.forEach(script => {
      const newScript = document.createElement('script')
      newScript.textContent = script.textContent
      document.head.appendChild(newScript)
    })

    // 4. 按顺序加载公共JS
    const publicJs = jsLinks.filter(item => !item.isPrivate)
    for (const item of publicJs) {
      await new Promise((resolve, reject) => {
        const existingScript = document.head.querySelector(`script[src="${item.path}"]`)
        if (existingScript) {
          existingScript.remove()
        }
        const newScript = document.createElement('script')
        newScript.src = item.path
        newScript.onload = resolve
        newScript.onerror = reject
        document.head.appendChild(newScript)
      })
    }

    // 5. 按顺序加载私有JS
    const privateJs = jsLinks.filter(item => item.isPrivate)
    for (const item of privateJs) {
      await new Promise((resolve, reject) => {
        const existingScript = document.head.querySelector(`script[src="${item.path}"]`)
        if (existingScript) {
          existingScript.remove()
        }
        const newScript = document.createElement('script')
        newScript.src = item.path
        newScript.onload = resolve
        newScript.onerror = reject
        document.head.appendChild(newScript)
      })
    }

    // 6. 触发下一个组件更新和鼠标绑定事件
    if (props.initRender) {
      emits('nextRender')
    }
    emits('mouseEvent')

    // 7. 新增组件的时候需要将组件定位到视口中
    if (props.element.isNew) {
      const element = document.querySelector('.setting-web-body')?.querySelector(`.setting-web-component-${props.index}`)
      element.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'center'
      })
      delete store.state.template.componentList[props.index].isNew
    }
  } catch (error) {
    ElMessage.error(`组件${props.element.name}脚本加载出错，请检查模板代码`)
    console.error(error)
    emits('nextRender')
  }
}

// 编辑组件
const handleEdit = async () => {
  const editorElement = contentRef.value.querySelector('.ruifox-editor')
  if (!editorElement) {
    ElMessage.warning('未找到可编辑元素')
    return
  }

  const key = editorElement.getAttribute('_key')
  if (!key) {
    ElMessage.warning('当前元素未注明_key属性，请检查模板代码')
    return
  }

  // 如果组件id发生变化，则获取组件的默认变量
  if (props.element.id != store.state.template.componentId) {
    const res = await getTemplateDetail({ id: props.element.id })
    store.commit('template/SET_COMPONENT_VARIABLE_DEFAULT', res.data.properties)
  }
  store.commit('template/SET_COMPONENT_VARIABLE', {
    id: props.element.id,
    key: props.element.componentKey
  })
  store.commit('template/SET_EDITOR_VARIABLE', key)
  store.commit('template/SET_SHOW_CONFIG_PANEL', true)
  store.commit('template/SET_SHOW_TOOL_PANEL', false)
  store.commit('template/SET_EDITOR_DOM', editorElement)
}

// 上移组件
const handleMoveUp = () => {
  const list = store.state.template.componentList
  const temp = list[props.index]
  list[props.index] = list[props.index - 1]
  list[props.index - 1] = temp
  store.commit('template/SET_COMPONENT_LIST', list)
}

// 下移组件
const handleMoveDown = () => {
  const list = store.state.template.componentList
  const temp = list[props.index]
  list[props.index] = list[props.index + 1]
  list[props.index + 1] = temp
  store.commit('template/SET_COMPONENT_LIST', list)
}

// 删除组件
const handleDelete = () => {
  ElMessageBox.confirm(
    '确定删除该组件吗？',
    '提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    }
  )
    .then(() => {
      const list = store.state.template.componentList
      const categoryVariable = store.state.template.categoryVariable
    
      // 删除变量
      const deleteVariables = (data) => {
        delete categoryVariable[data.componentKey]
        if (data.children) {
          data.children.forEach(item => deleteVariables(item))
        }
      }
    
      deleteVariables(props.element)
      list.splice(props.index, 1)
      store.commit('template/SET_COMPONENT_LIST', list)
    }).catch(() => {
      ElMessage.info('已取消删除')
    })
}

// 监听组件是否进入视口，用于加载组件资源
const observer = ref(null)
const setupIntersectionObserver = () => {
  observer.value = new IntersectionObserver(async (entries) => {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        // 初始化渲染过程中不要触发，否则nextRender会执行多次，打乱渲染顺序
        if (!props.initRender) {
          await loadComponentScripts()
          observer.value.unobserve(entry.target)
        }
      }
    }
  }, {
    threshold: 0.5  // 元素进入视口就触发
  })

  if (contentRef.value) {
    observer.value.observe(contentRef.value)
  }
}

// 修改onMounted钩子
onMounted(async () => {
  try {
    await renderContent()
    await loadComponentScripts()
    setupIntersectionObserver()
  } catch (error) {
    console.error(`组件${props.index + 1}初始化失败:`, error)
  }
})

onUnmounted(() => {
  observer.value.disconnect()
})
</script>

<style lang="scss" scoped>
.setting-web-component {
  position: relative;
  width: 100%;

  .layout-add-box {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px 0;
  }
}

.toolbar {
  position: absolute;
  z-index: 1998;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  display: none;
  height: 30px;
  border-radius: 0px 0px 4px 4px;
  overflow: hidden;
  background-color: rgba(0, 0, 0, 0.7);

  .toolbar-wrap {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    line-height: 30px;
    color: #fffc;

    i {
      padding: 0 6px;
      font-size: 14px;
      cursor: pointer;

      &:hover {
        color: #fff;
      }
    }
  }
}
</style>